﻿using Ding.Helpers;
using Ding.Helpers.Internal;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Text;
using Ding.Caches;
using Ding.Logs.Extensions;
using Ding.Logs;
using System.Web;
using System.IO;
using System.Collections.Specialized;
using Ding.Encryption;
using Microsoft.Extensions.Options;
#if __CORE20__
using Microsoft.AspNetCore.Http.Internal;
#endif
#if __CORE21__
using Microsoft.AspNetCore.Http;
#endif

namespace Ding.Webs.ApiSign
{
    /// <summary>
    /// 签名认证
    /// </summary>
    public class SignatureAttribute : ActionFilterAttribute
    {
        public bool IsCheck { get; set; }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!IsCheck)
            {
                return;
            }

            var request = context.HttpContext.Request;
            var signatureHeader = new SignatureHeader(request.Headers);

            // 判断请求头
            if (!signatureHeader.Verify())
            {
                // 请求头部不完整
                context.Result = new DResult(StateCode.Ok, "", new { errcode = 403, errmsg = "请求头部不完整" });
                return;
            }

            var cache = Ioc.Create<ICache>();
            if (cache.Exists(signatureHeader.Signature))
            {
                // 重复提交
                context.Result = new DResult(StateCode.Ok, "", new { errcode = 403, errmsg = "重复提交" });
                return;
            }

            if (IsTimeout(signatureHeader.Timestamp))
            {
                // 判断请求是否过期
                context.Result = new DResult(StateCode.Ok, "", new { errcode = 403, errmsg = "请求已过期" });
                return;
            }

            var ApiSignOptions = Ioc.Create<IOptions<ApiSignOptions>>();

            string appsecret;
            try
            {
                var appid = cache.Get<string>("appid");
                appsecret = cache.Get<string>("appsecret");

                if (appid.StrIsNullOrEmpty() || appsecret.StrIsNullOrEmpty())
                {
                    var rsaCrypto = new RSACrypto(ApiSignOptions.Value.RsaPrivateKey, ApiSignOptions.Value.RsaPublicKey);
                    appid = rsaCrypto.Decrypt(signatureHeader.AppId);
                    appsecret = rsaCrypto.Decrypt(signatureHeader.AppSecret);
                    if (appid.StrIsNullOrEmpty() || appsecret.StrIsNullOrEmpty())
                    {
                        context.Result = new DResult(StateCode.Fail, "", new { errcode = 403, errmsg = "检验失败,请联系管理员" });
                    }
                    cache.Add("appid", appid, TimeSpan.FromDays(1));
                    cache.Add("appsecret", appsecret, TimeSpan.FromDays(1));
                }
                signatureHeader.AppId = appid;
                signatureHeader.AppSecret = appsecret;
            }
            catch (Exception ex)
            {
                ex.Log(Ding.Logs.Log.GetLog().Caption("检验失败"));
                // 密钥解密失败
                context.Result = new DResult(StateCode.Fail, "", new { errcode = 403, errmsg = "检验失败,请联系管理员" });
                return;
            }

            // 根据请求类型拼接参数
            var method = request.Method;
            var form = HttpUtility.ParseQueryString(request.QueryString.Value);
            var data = string.Empty;
            switch (method)
            {
                case "POST":
                case "PUT":
#if __CORE21__
                    request.EnableBuffering();
#else
                    request.EnableRewind();
#endif
                    using (var buffer = new MemoryStream())
                    {
                        request.Body.Position = 0;
                        request.Body.CopyTo(buffer);
                        data = Encoding.UTF8.GetString(buffer.ToArray());
                    }
                    break;
                case "GET":
                case "DELETE":
                    data = GetSignQueryString(form);
                    break;
                default:
                    context.Result = new DResult(StateCode.Fail, "", new { errcode = 403, errmsg = $"不允许{method}请求" });
                    return;
            }

            //// 签名数据（顺序：请求参数 -> 应用id -> 时间戳 -> 随机数）
            //var signData = data + signatureHeader.ToString();
            // 调整简单的签名数据，方便与前端对接（顺序：token -> 应用id -> 时间戳 -> 随机数）
            var signData = context.HttpContext.Items["token"].ToString() + signatureHeader.ToString();
            //Console.WriteLine($"调试检验码：token({context.HttpContext.Items["token"].ToString()})------appid({appid})------appsecret({appsecret})------timestamp({signatureHeader.Timestamp})------nonce({signatureHeader.Nonce})");
            if (!Signature.Verify(signData, appsecret, signatureHeader.Signature))
            {
                // 无效签名
                context.Result = new DResult(StateCode.Fail, "", new { errcode = 403, errmsg = $"无效签名" });
                return;
            }
            else
            {
                // 插入缓存，用于判断是否重复提交
                cache.Add(signatureHeader.Signature, signatureHeader.Signature, TimeSpan.FromSeconds(ApiSignOptions.Value.RequestExpireTime));
            }

            base.OnActionExecuting(context);
        }

        /// <summary>
        /// 是否超时
        /// </summary>
        /// <param name="timestamp"></param>
        /// <returns></returns>
        private bool IsTimeout(string timestamp)
        {
            if (!double.TryParse(timestamp, out double ts1)) return true;

            //Console.WriteLine($"当前时间{DateTime.Now}----时间戳{Time.GetTimeFromUnixTimestamp(ts1.ToLong()/1000)}({timestamp})");

            var ts2 = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
            var ts = ts2 - ts1;

            var ApiSignOptions = Ioc.Create<IOptions<ApiSignOptions>>();
            var vs = ApiSignOptions.Value.RequestExpireTime;  // 请求过期时间（秒）

            return ts > vs * 1000d;
        }

        /// <summary>
        /// 拼接加密用的请求参数
        /// </summary>
        /// <param name="query">url参数。</param>
        /// <returns></returns>
        private string GetSignQueryString(NameValueCollection query)
        {
            // 按Key的字母顺序排序
            var sortedParams = GetSortedDictionary(query);
            var sb = new StringBuilder();
            // 把所有参数名和参数值串在一起
            foreach (var item in sortedParams)
            {
                sb.Append(item.Key).Append(item.Value);
            }
            return sb.ToString();
        }

        /// <summary>
        /// 获取排序的键值对
        /// </summary>
        /// <param name="nvc"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        private SortedDictionary<string, string> GetSortedDictionary(NameValueCollection nvc, Func<string, bool> filter = null)
        {
            SortedDictionary<string, string> dic = new SortedDictionary<string, string>();
            if (nvc != null && nvc.Count > 0)
            {
                foreach (var k in nvc.AllKeys)
                {
                    if (filter == null || !filter(k))
                    {//如果没设置过滤条件或者无需过滤  
                        dic.Add(k, nvc[k]);
                    }
                }
            }
            return dic;
        }

    }
}
